#include "sockio.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <string.h>
#include <stdio.h>
#include <errno.h>

#include <unistd.h>

#include <assert.h>

int sockopen(const char* host, int _port, enum socktype type)
{
  if (!host)
    return -1;
  
  int status = 0;
  int sfd = -1; /* socket fd */
  
  char port[8];
  sprintf(port, "%d", _port);
  
  struct addrinfo hints;
  struct addrinfo * info;
  
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = (type == SOCKET_TCP) ? SOCK_STREAM : SOCK_DGRAM;
  
  status = getaddrinfo(host, port, &hints, &info);
  
  if (status != 0) {
    fprintf(stderr, "Erro buscando host %s: %s\n", host, gai_strerror(status));
    return -1;
  }
  
  sfd = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
  
  if (sfd < 0) {
    fprintf(stderr, "Erro ao criar socket para o host %s:%s: %s\n", host, port,
            strerror(errno));
    freeaddrinfo(info);
    return -1;
  }

  /* conecte, independente de ser udp */
  status = connect(sfd, info->ai_addr, info->ai_addrlen);
  if (status < 0) {
    fprintf(stderr, "Erro ao conectar com o host %s:%s: %s\n", host, port,
            strerror(errno));
    freeaddrinfo(info);
    return -1;
  }
  
  /* nesse ponto, a conexao estah ok */
  freeaddrinfo(info);
  return sfd;
}

int sockclose (int id)
{
  return (close(id));
}


int socklisten(int _port, int backlog, enum socktype type)
{
  int yes=1;
  struct addrinfo hints, * ai, *p;
  int status;
  int fd;
  char port[8];
  
  
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = (type == SOCKET_TCP) ? SOCK_STREAM : SOCK_DGRAM;
  hints.ai_flags = AI_PASSIVE;
  
  sprintf(port, "%d", _port);
  
  if ((status = getaddrinfo("127.0.0.1", port, &hints, &ai)) != 0)
  {
      fprintf(stderr, "Erro ao criar listener: %s\n", gai_strerror(status));
      goto error;
  }
  for(p = ai; p != NULL; p = p->ai_next)
  {
      fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
      if (fd < 0) {
          perror("socklisten warning");
          continue;
      }
      setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
      if (bind(fd, p->ai_addr, p->ai_addrlen) < 0) {
          close(fd);
          perror("socklisten BIND warning");
          continue;
      }
      break;
  }
  
  if (p == NULL)
    goto error;
  
  /* thas-it. */
  if (type == SOCKET_UDP)
    goto success;
  
  if ((status = listen(fd, backlog)) < 0)
    goto error;


  success:
  {
    freeaddrinfo(ai);
    return fd;
  }
  
  error:
  {
    if (ai != 0)
      freeaddrinfo(ai);
    if (errno)
    {
      if (errno != EINTR)
      {
        perror("Nao foi possivel criar listener");
      }
      else
      {
        status = 0;
      }
    }
    if (fd > 0)
      close(fd);
    
    return status;
  }
}

int sockaccept(int id)
{
  struct addrinfo remoteaddr;
  size_t addrlen = sizeof(remoteaddr);
  return accept(id, (struct sockaddr *)&remoteaddr, &addrlen);
}

int sockwrite(int id, const char* buf, unsigned int len)
{
  int out, total;
  assert(buf != NULL);
  assert(id > 0);
  if (len <= 0) return 0;

  out = send(id, buf, len, 0);
  if (out < 0) {
    fprintf(stderr, "Erro ao escrever no socket: %s\n", strerror(errno));
    total = -1;
  }

  return out;
}

int sockread(int id, char *buf, unsigned int len)
{
  int in;
  assert(buf != NULL);
  assert(id > 0);
  assert(len > 0);
  in = recv(id, buf, len, 0);
  if (in < 0)
  {
    if (errno != EWOULDBLOCK)
      perror("Erro ao ler do socket");
  }
  return in;
}


